/*
 * Copyright (c) 2023-2024 elsfs Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.elsfs.cloud.extended.screw.query;

import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import javax.sql.DataSource;
import lombok.Getter;
import org.elsfs.cloud.common.util.exception.QueryException;
import org.elsfs.cloud.common.util.lang.Assert;
import org.elsfs.cloud.common.util.lang.ExceptionUtils;
import org.elsfs.cloud.common.util.lang.StringUtils;
import org.elsfs.cloud.common.util.sql.DatabaseType;
import org.elsfs.cloud.common.util.sql.JdbcUtils;
import org.elsfs.cloud.extended.screw.constant.ScrewConstants;
import org.elsfs.cloud.extended.screw.metadata.Column;
import org.elsfs.cloud.extended.screw.metadata.PrimaryKey;

/**
 * AbstractDataBaseQuery
 *
 * @author zeng
 */
public abstract class AbstractDatabaseQuery implements DatabaseQuery {
  /** 缓存 */
  protected final Map<String, List<Column>> columnsCaching = new ConcurrentHashMap<>();

  /** DataSource */
  @Getter private final DataSource dataSource;

  /** Connection 双重锁，线程安全 */
  protected volatile Connection connection;

  public AbstractDatabaseQuery(DataSource dataSource) {
    this.dataSource = dataSource;
  }

  /**
   * 获取连接对象，单例模式，采用双重锁检查
   *
   * @return {@link Connection}
   * @throws QueryException QueryException
   */
  private Connection getConnection() throws QueryException {
    try {
      // 不为空
      if (!Objects.isNull(connection) && !connection.isClosed()) {
        System.out.println(connection);
        return connection;
      }
      // 同步代码块
      synchronized (AbstractDatabaseQuery.class) {
        // 为空或者已经关闭
        if (Objects.isNull(connection) || connection.isClosed()) {
          this.connection = this.getDataSource().getConnection();
        }
      }
      return this.connection;
    } catch (SQLException e) {
      throw ExceptionUtils.mpe(e);
    }
  }

  /**
   * 获取 getCatalog
   *
   * @return {@link String}
   * @throws QueryException QueryException
   */
  protected String getCatalog() throws QueryException {
    try {
      String catalog = this.getConnection().getCatalog();
      if (StringUtils.isBlank(catalog)) {
        return null;
      }
      return catalog;
    } catch (SQLException e) {
      throw ExceptionUtils.mpe(e);
    }
  }

  /**
   * 获取 getSchema
   *
   * @return {@link String}
   * @throws QueryException QueryException
   */
  protected String getSchema() throws QueryException {
    try {
      String schema;
      // 获取数据库URL 用于判断数据库类型
      String url = this.getConnection().getMetaData().getURL();
      // 获取数据库名称
      String name = JdbcUtils.getDbType(url).getName();
      if (DatabaseType.CACHEDB.getName().equals(name)) {
        schema = verifySchema(this.getDataSource());
      } else {
        schema = this.getConnection().getSchema();
      }

      if (StringUtils.isBlank(schema)) {
        return null;
      }
      return schema;
    } catch (SQLException e) {
      throw ExceptionUtils.mpe(e);
    }
  }

  /**
   * 验证Schema
   *
   * @param dataSource {@link DataSource}
   * @return Schema
   */
  private String verifySchema(DataSource dataSource) throws SQLException {
    String schema;
    if (dataSource instanceof HikariDataSource) {
      schema = ((HikariDataSource) dataSource).getSchema();
    } else {
      schema = dataSource.getConnection().getSchema();
    }

    // 验证是否有此Schema
    ResultSet resultSet = this.getConnection().getMetaData().getSchemas();
    while (resultSet.next()) {
      int columnCount = resultSet.getMetaData().getColumnCount();
      for (int i = 1; i <= columnCount; i++) {
        String columnValue = resultSet.getString(i);
        if (StringUtils.isNotBlank(columnValue) && columnValue.contains(schema)) {
          return schema;
        }
      }
    }
    return null;
  }

  /**
   * 获取 DatabaseMetaData
   *
   * @return {@link DatabaseMetaData}
   * @throws QueryException QueryException
   */
  protected DatabaseMetaData getMetaData() throws QueryException {
    try {
      return this.getConnection().getMetaData();
    } catch (SQLException e) {
      throw ExceptionUtils.mpe(e);
    }
  }

  /**
   * 准备声明
   *
   * @param sql {@link String} SQL
   * @return {@link PreparedStatement}
   * @throws QueryException QueryException
   */
  protected PreparedStatement prepareStatement(String sql) throws QueryException {
    Assert.notEmpty(sql, "Sql can not be empty!");
    try {
      return this.getConnection().prepareStatement(sql);
    } catch (SQLException e) {
      throw ExceptionUtils.mpe(e);
    }
  }

  /**
   * 根据表名获取主键
   *
   * @return {@link List}
   * @throws QueryException QueryException
   */
  @Override
  public List<? extends PrimaryKey> getPrimaryKeys() throws QueryException {
    throw ExceptionUtils.mpe(ScrewConstants.NOT_SUPPORTED);
  }
}
